/*
 * $HeadURL: https://svn.apache.org/repos/asf/jakarta/httpcomponents/oac.hc3x/tags/HTTPCLIENT_3_1/src/java/org/apache/commons/httpclient/util/ParameterFormatter.java $
 * $Revision: 480424 $
 * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
 *
 * ====================================================================
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package org.apache.commons.httpclient.util;

import org.apache.commons.httpclient.NameValuePair;

/**
 * <p>
 *  This formatter produces a textual representation of attribute/value pairs. It 
 *  comforms to the generic grammar and formatting rules outlined in the 
 *  <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.1">Section 2.1</a>
 *  and  
 *  <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6</a>
 *  of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>
 * </p>
 * <h>2.1 Augmented BNF</h>
 * <p>
 *  Many HTTP/1.1 header field values consist of words separated by LWS or special 
 *  characters. These special characters MUST be in a quoted string to be used within 
 *  a parameter value (as defined in section 3.6).
 * <p>
 * <pre>
 * token          = 1*<any CHAR except CTLs or separators>
 * separators     = "(" | ")" | "<" | ">" | "@"
 *                | "," | ";" | ":" | "\" | <">
 *                | "/" | "[" | "]" | "?" | "="
 *                | "{" | "}" | SP | HT
 * </pre>
 * <p>
 *  A string of text is parsed as a single word if it is quoted using double-quote marks.
 * </p>
 * <pre>
 * quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )
 * qdtext         = <any TEXT except <">>
 * </pre>
 * <p>
 *  The backslash character ("\") MAY be used as a single-character quoting mechanism only 
 *  within quoted-string and comment constructs.
 * </p>
 * <pre>
 * quoted-pair    = "\" CHAR
 * </pre>
 * <h>3.6 Transfer Codings</h>
 * <p>
 *  Parameters are in the form of attribute/value pairs.
 * </p>
 * <pre>
 * parameter               = attribute "=" value
 * attribute               = token
 * value                   = token | quoted-string
 * </pre>
 * 
 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
 * 
 * @since 3.0
 */
public class ParameterFormatter {

	/**
	 * Special characters that can be used as separators in HTTP parameters.
	 * These special characters MUST be in a quoted string to be used within a
	 * parameter value
	 */
	private static final char[] SEPARATORS = { '(', ')', '<', '>', '@', ',',
			';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t' };

	/**
	 * Unsafe special characters that must be escaped using the backslash
	 * character
	 */
	private static final char[] UNSAFE_CHARS = { '"', '\\' };

	/**
	 * This flag determines whether all parameter values must be enclosed in
	 * quotation marks, even if they do not contain any special characters
	 */
	private boolean alwaysUseQuotes = true;

	/** Default ParameterFormatter constructor */
	public ParameterFormatter() {
		super();
	}

	private static boolean isOneOf(char[] chars, char ch) {
		for (int i = 0; i < chars.length; i++) {
			if (ch == chars[i]) {
				return true;
			}
		}
		return false;
	}

	private static boolean isUnsafeChar(char ch) {
		return isOneOf(UNSAFE_CHARS, ch);
	}

	private static boolean isSeparator(char ch) {
		return isOneOf(SEPARATORS, ch);
	}

	/**
	 * Determines whether all parameter values must be enclosed in quotation
	 * marks, even if they do not contain any special characters
	 * 
	 * @return <tt>true</tt> if all parameter values must be enclosed in
	 *         quotation marks, <tt>false</tt> otherwise
	 */
	public boolean isAlwaysUseQuotes() {
		return alwaysUseQuotes;
	}

	/**
	 * Defines whether all parameter values must be enclosed in quotation marks,
	 * even if they do not contain any special characters
	 * 
	 * @param alwaysUseQuotes
	 */
	public void setAlwaysUseQuotes(boolean alwaysUseQuotes) {
		this.alwaysUseQuotes = alwaysUseQuotes;
	}

	/**
	 * Formats the given parameter value using formatting rules defined in RFC
	 * 2616
	 * 
	 * @param buffer
	 *            output buffer
	 * @param value
	 *            the parameter value to be formatted
	 * @param alwaysUseQuotes
	 *            <tt>true</tt> if the parameter value must be enclosed in
	 *            quotation marks, even if it does not contain any special
	 *            characters<tt>, false</tt> only if the parameter value
	 *            contains potentially unsafe special characters
	 */
	public static void formatValue(final StringBuffer buffer,
			final String value, boolean alwaysUseQuotes) {
		if (buffer == null) {
			throw new IllegalArgumentException("String buffer may not be null");
		}
		if (value == null) {
			throw new IllegalArgumentException("Value buffer may not be null");
		}
		if (alwaysUseQuotes) {
			buffer.append('"');
			for (int i = 0; i < value.length(); i++) {
				char ch = value.charAt(i);
				if (isUnsafeChar(ch)) {
					buffer.append('\\');
				}
				buffer.append(ch);
			}
			buffer.append('"');
		} else {
			int offset = buffer.length();
			boolean unsafe = false;
			for (int i = 0; i < value.length(); i++) {
				char ch = value.charAt(i);
				if (isSeparator(ch)) {
					unsafe = true;
				}
				if (isUnsafeChar(ch)) {
					buffer.append('\\');
				}
				buffer.append(ch);
			}
			if (unsafe) {
				buffer.insert(offset, '"');
				buffer.append('"');
			}
		}
	}

	/**
	 * Produces textual representaion of the attribute/value pair using
	 * formatting rules defined in RFC 2616
	 * 
	 * @param buffer
	 *            output buffer
	 * @param param
	 *            the parameter to be formatted
	 */
	public void format(final StringBuffer buffer, final NameValuePair param) {
		if (buffer == null) {
			throw new IllegalArgumentException("String buffer may not be null");
		}
		if (param == null) {
			throw new IllegalArgumentException("Parameter may not be null");
		}
		buffer.append(param.getName());
		String value = param.getValue();
		if (value != null) {
			buffer.append("=");
			formatValue(buffer, value, this.alwaysUseQuotes);
		}
	}

	/**
	 * Produces textual representaion of the attribute/value pair using
	 * formatting rules defined in RFC 2616
	 * 
	 * @param param
	 *            the parameter to be formatted
	 * 
	 * @return RFC 2616 conformant textual representaion of the attribute/value
	 *         pair
	 */
	public String format(final NameValuePair param) {
		StringBuffer buffer = new StringBuffer();
		format(buffer, param);
		return buffer.toString();
	}

}
